/**
 * timbap audio simulator by andreas pabst

 * sends osc control messages to the timbap visualizer
 * in order to simulate the timbap audio component
 * http://www.timbap.de
 *
 * press <UP> and <DOWN> arrow keys to control speed and
 * direction of the simulated turntable
 *
 * press <SPACE> to halt and restart the simulated timecode
 *
 * press any key between <a> and <z> to simulate resetting 
 * the needle to an arbitrary position between the start (A)
 * and the end (Z) of the record.
 *
 *
 */

import ddf.minim.AudioPlayer;
import ddf.minim.Minim;
import netP5.NetAddress;
import oscP5.OscMessage;
import oscP5.OscP5;
import processing.core.PApplet;
import processing.core.PFont;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

public class TimbapAudioSimulator extends PApplet {

    OscP5 oscP5;
    NetAddress myRemoteLocation;
    PFont defaultFont;

    float absPosition = 0;
    boolean isTurning = true;
    boolean isFlipSide = true;
    boolean resetInProgress = false;
    int absPositionDelta = 10;
    int maxAbsPositionDelta = 30;
    float maxAbsPosition = 65536;
    String currentlyPlayedFile = "none";
    int lengthOfSilenceBeforeReset = 8;
    int lengthOfSilenceBeforeResetCounter = 0;
    float fractionPlayed = 0;

    int sendingPort = 3001;
    int listeningPort = 3002;

    boolean audioDeactivated = true;

    AudioPlayer audioPlayer;

    public void setup() {

        for (listeningPort = 3002; listeningPort < 3012; listeningPort += 2) {
            try {
                java.net.DatagramSocket ds = new java.net.DatagramSocket(listeningPort);
                ds.close();
                System.out.println("UDP Port " + listeningPort + " is open.");
                break;
            }
            catch (IOException ex) {
                System.out.println("UDP Port " + listeningPort + " is occupied. Trying next ...");
            }
        }
        sendingPort = listeningPort - 1;
        if (listeningPort == 3002) {
            audioDeactivated = false;
        }

        frame.setIconImage(new javax.swing.ImageIcon(loadBytes("timbap_audio_icon.png")).getImage());
        frame.setTitle("timbap audio simulator "
                + "(" + sendingPort + "," + listeningPort + ")");

        size(280,450);

        frameRate(50);
        oscP5 = new OscP5(this,listeningPort);
        myRemoteLocation = new NetAddress("127.0.0.1",sendingPort);

        defaultFont = loadFont("Sylfaen-16.vlw");
        textFont(defaultFont);

        String title = "timbap audio simulator";

        String description =
                "sends osc control messages\n" +
                        "to the timbap visualizer\n" +
                        "in order to simulate the\n" +
                        "timbap audio component\n" +
                        "http://www.timbap.de";

        String upAndDownKeyDescription =
                "Press <UP> and <DOWN> arrow keys\n" +
                        "to control speed and direction\n" +
                        "of the simulated turntable";

        String spaceKeyDescription =
                "Press <SPACE> to halt and restart\n" +
                        "the simulated timecode";

        String aToZKeyDescription =
                "Press any key between <A> and <Z>\n" +
                        "to simulate resetting the needle\n" +
                        "to an arbitrary position between the\n" +
                        "start (A) and the end (Z) of the record.";

        String enterKeyDescription =
                "Press <ENTER> to flip between \n" +
                        "front side and flip side.";

        smooth();
        background(150,100,100);
        fill(255);
        text(title,40,30);
        fill(230,180,180);
        text(description,40,60);
        fill(255);
        text(upAndDownKeyDescription,10,180);
        text(spaceKeyDescription,10,250);
        text(enterKeyDescription,10,300);
        text(aToZKeyDescription,10,350);

        if (audioDeactivated) {
            fill(200,0,0);
            rect(10,370,250,20);
            fill(255);
            text("AUDIO PLAYBACK IS DISABLED",15,385);
        }

        if (!audioDeactivated) {
            Minim.start(this);
        }
    }

    public void keyPressed() {

        if (keyCode == DOWN && absPositionDelta > -maxAbsPositionDelta) {
            absPositionDelta--;
        }
        else if (keyCode == UP && absPositionDelta < maxAbsPositionDelta) {
            absPositionDelta++;
        }
        else if (key == ENTER) {
            isFlipSide = !isFlipSide;
        }
        else if (key == ' ') {
            isTurning = !isTurning;
        }
        else if (key >= 'a' && key <= 'z') {
            float jumpIndex = (float)((int)key - (int)'a') / ((int)'z' - (int)'a');
            absPosition = jumpIndex * maxAbsPosition * 0.99f;
            resetInProgress = true;
        }
    }

    public void draw() {

        if (absPosition > maxAbsPosition) {
            absPosition = 0;
        }
        if (absPosition < 0) {
            absPosition = maxAbsPosition - 1;
        }

        float normAbsPositionDelta = absPositionDelta * 0.1f;
        float velocity = 0.0f;
        float power = 0.0f;

        if (!resetInProgress) {
            absPosition = absPosition + normAbsPositionDelta;
            velocity = (float)(normAbsPositionDelta - (Math.random() * 0.01f));
            power = min(abs(velocity), 1.0f);
        } else {
            if (lengthOfSilenceBeforeResetCounter < lengthOfSilenceBeforeReset) {
                lengthOfSilenceBeforeResetCounter++;
            } else {
                resetInProgress = false;
                lengthOfSilenceBeforeResetCounter = 0;
                if (audioPlayer != null) {
                    System.out.print("Cueing to reset position ... ");
                    audioPlayer.cue((int)((absPosition / maxAbsPosition) * audioPlayer.length()));
                    System.out.println("finished.");
                }
            }
        }

        if (!isTurning) {
            absPosition = absPosition - normAbsPositionDelta;
            velocity = power = 0;
            if (audioPlayer != null && audioPlayer.isPlaying()) {
                audioPlayer.pause();
            }
        } else {
            if (audioPlayer != null) {
                if (!audioPlayer.isPlaying()) {
                    audioPlayer.play();
                }
                fractionPlayed = audioPlayer.position() / (float)audioPlayer.length();
            }
        }

        sendSeparate(power, velocity, absPosition, fractionPlayed);
    }

    private void sendSeparate(float pSignalPower, float pVelocity, float pAbsPosition, float pFractionPlayed) {

        OscMessage absPositionMessage = new OscMessage("/timbap_position");
        OscMessage velocityMessage = new OscMessage("/timbap_velocity");
        OscMessage powerMessage = new OscMessage("/timbap_power");
        OscMessage fractionPlayedMessage = new OscMessage("/played");

        float tAbsPosition = isFlipSide ? pAbsPosition : maxAbsPosition + pAbsPosition;
        absPositionMessage.add(tAbsPosition);
        velocityMessage.add(pVelocity);
        powerMessage.add(pSignalPower);
        fractionPlayedMessage.add(pFractionPlayed);

        oscP5.send(absPositionMessage, myRemoteLocation);
        oscP5.send(velocityMessage, myRemoteLocation);
        oscP5.send(powerMessage, myRemoteLocation);
        oscP5.send(fractionPlayedMessage, myRemoteLocation);
    }

    public void oscEvent(OscMessage pOscMessage) {

        if (pOscMessage != null) {
            String tAddrPattern = pOscMessage.addrPattern();
            Object[] tArguments = pOscMessage.arguments();
            if (tAddrPattern != null && tAddrPattern.equals("/loadfile")) {
                if (tArguments != null && tArguments.length == 1) {
                    String tReceivedFileURIString = (String)tArguments[0];
                    System.out.println("received: /loadFile s (" + tReceivedFileURIString + ")");
                    if (!audioDeactivated) {
                        try {
                            File tCurrentlyPlayedFile = new File(new URI(tReceivedFileURIString));
                            currentlyPlayedFile = tCurrentlyPlayedFile.getAbsolutePath();
                            audioPlayer = Minim.loadFile(tCurrentlyPlayedFile.getAbsolutePath());
                        } catch (URISyntaxException e) {
                            if (audioPlayer!= null) {
                                audioPlayer.pause();
                            }
                            currentlyPlayedFile = tReceivedFileURIString;
                            audioPlayer = null;
                        }
                    }
                    fractionPlayed = 0;
                }
            }
        }

    }


    public void stop() {
        if (audioPlayer != null) {
            audioPlayer.close();
        }
        super.stop();
    }


    public static void main(String args[]) {

        String[] pAppletParams = new String[] {"TimbapAudioSimulator"};
        PApplet.main(pAppletParams);

    }


}

